#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Threading;
using Castle.Windsor;
using Grawlix.Services;
using log4net;

namespace Grawlix.Client
{
	/// <summary>
	/// The default method for loading the game.
	/// </summary>
	[Serializable]
	public class DefaultGameLoader : ServiceBase<IGameLoader>,
	                                 IGameLoader
	{
		/// <summary>
		/// The name for the built-in world.
		/// </summary>
		private const string BuiltInWorldName = "Built-In";

		/// <summary>
		/// The name for the default world.
		/// </summary>
		private const string DefaultWorldName = "Default";

		/// <summary>
		/// The game loader output log.
		/// </summary>
		private static readonly ILog _log =
			LogManager.GetLogger(typeof(DefaultGameLoader));

		/// <summary>
		/// The factory used to construct games.
		/// </summary>
		private readonly IGameKernelFactory _gameKernelFactory;

		/// <summary>
		/// The name of the local world.
		/// </summary>
		private readonly string _localWorldName;

		/// <summary>
		/// The object to synchronize on.
		/// </summary>
		private readonly object _sync = new object();

		/// <summary>
		/// The currently active game.
		/// </summary>
		private ExternalProxy<IGameKernel> _currentKernel;

		/// <summary>
		/// Whether a quit request has been made.
		/// </summary>
		private bool _hasQuit;

		/// <summary>
		/// The game that is currently being loaded.
		/// </summary>
		private ExternalProxy<IGameKernel> _loadingKernel;

		/// <summary>
		/// The kernel to use as parent.
		/// </summary>
		private IWindsorContainer _parentKernel;

		/// <summary>
		/// Initializes a new instance of the 
		/// <see cref="DefaultGameLoader"/> class.
		/// </summary>
		/// <param name="gameKernelFactory">The game factory used to
		/// construct new <see cref="IGameKernel"/> instances.</param>
		/// <exception cref="ArgumentNullException"><c>gameKernelFactory</c>
		/// is null.</exception>
		public DefaultGameLoader(
			IGameKernelFactory gameKernelFactory)
			: this(DefaultWorldName, gameKernelFactory)
		{
		}

		/// <summary>
		/// Initializes a new instance of the 
		/// <see cref="DefaultGameLoader"/> class.
		/// </summary>
		/// <param name="localWorldName">Name of the local world to
		/// load.
		/// </param>
		/// <param name="gameKernelFactory">The factory used to create new
		/// games.</param>
		/// <exception cref="ArgumentNullException"><c>gameKernelFactory</c>
		/// is null.</exception>
		/// <exception cref="ArgumentNullException">
		/// <c>localWorldName</c> is null.</exception>
		public DefaultGameLoader(string localWorldName,
		                         IGameKernelFactory
		                         	gameKernelFactory)
		{
			if (localWorldName == null)
				throw new ArgumentNullException("localWorldName");
			if (gameKernelFactory == null)
				throw new ArgumentNullException("gameKernelFactory");
			_localWorldName = localWorldName;
			_gameKernelFactory = gameKernelFactory;
		}

		/// <summary>
		/// The kernel that is currently active.
		/// </summary>
		/// <value>
		/// The kernel that is currently active, or null if none
		/// are ready.
		/// </value>
		public IGameKernel CurrentKernel
		{
			get
			{
				if (_currentKernel == null)
					return null;
				return _currentKernel.Target;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the user has requested to
		/// quit.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this the user has requested to quit;
		/// 	otherwise,
		/// <c>false</c>.
		/// </value>
		public bool HasQuit
		{
			get
			{
				Thread.MemoryBarrier();
				return _hasQuit;
			}
		}

		/// <summary>
		/// Activates using the specified parent kernel.
		/// </summary>
		/// <param name="parentKernel">The kernel to use as parent.</param>
		public void Activate(IWindsorContainer parentKernel)
		{
			_parentKernel = parentKernel;
			LoadLocal();
		}

		/// <summary>
		/// Cancels the current load.
		/// </summary>
		public void CancelLoad()
		{
			lock (_sync)
			{
				CancelLoadNoLock();
			}
		}

		/// <summary>
		/// Cancels the current load without locking.
		/// </summary>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"Exceptions relating to disposal can be ignored.")]
		private void CancelLoadNoLock()
		{
			try
			{
				if (_loadingKernel != null)
				{
					ExternalProxy<IGameKernel> proxy = _loadingKernel;
					_loadingKernel = null;
					proxy.Dispose();
				}
			}
			catch (Exception exception)
			{
				if (_log.IsWarnEnabled)
				{
					_log.Warn(
						"Hit an error during client disposal. " +
						"A memory leak might result.", exception);
				}
			}
		}

		/// <summary>
		/// Connects to a specified host.
		/// </summary>
		/// <param name="hostName">The name or IP address of the host.
		/// </param>
		/// <param name="port">The host's port.</param>
		/// <exception cref="ArgumentNullException"><c>hostName</c> is
		/// null.</exception>
		/// <exception cref="NotImplementedException">Loading a network
		/// game is not yet supported.</exception>
		/// <exception cref="ArgumentException">port must be between 1
		/// and 65535, inclusive.</exception>
		public void Connect(string hostName, int port)
		{
			if (hostName == null)
				throw new ArgumentNullException("hostName");
			lock (_sync)
			{
				if ((port <= 0) || (port > 0xffff))
				{
					throw new ArgumentException(
						"Valid ports are between 1 and 65535, inclusive. " +
						port + " was specified.", "port");
				}
				throw new NotImplementedException(
					"Connecting to a networked game is not yet supported.");
			}
		}

		/// <summary>
		/// Hosts the world with the specified name.
		/// </summary>
		/// <param name="worldName">Name of the world to load.</param>
		/// <param name="port">The port to host on.</param>
		/// <exception cref="ArgumentNullException"><c>worldName</c> is
		/// null.</exception>
		/// <exception cref="NotImplementedException">Loading a network
		/// game is not yet supported.</exception>
		/// <exception cref="ArgumentException">port must be between 1
		/// and 65535, inclusive.</exception>
		public void Host(string worldName, int port)
		{
			if (worldName == null)
				throw new ArgumentNullException("worldName");
			lock (_sync)
			{
				if ((port <= 0) || (port > 0xffff))
				{
					throw new ArgumentException(
						"Valid ports are between 1 and 65535, inclusive. " +
						port + " was specified.", "port");
				}
				throw new NotImplementedException(
					"Hosting a network game is not yet supported.");
			}
		}

		/// <summary>
		/// Determines whether the specified game is the Built-In
		/// client.
		/// </summary>
		/// <param name="game">The game to test.</param>
		/// <returns>
		/// 	<c>true</c> if the specified game is the Built-In client;
		/// 	otherwise, <c>false</c>.
		/// </returns>
		/// <exception cref="ArgumentNullException"><c>game</c> is
		/// null.</exception>
		private static bool IsBuiltInClient(IGameKernel game)
		{
			if (game == null)
				throw new ArgumentNullException("game");
			return (game.Name == BuiltInWorldName);
		}

		/// <summary>
		/// Determines whether the specified game is the default
		/// client.
		/// </summary>
		/// <param name="game">The game to test.</param>
		/// <returns>
		/// 	<c>true</c> if the specified game is the default client;
		/// 	otherwise, <c>false</c>.
		/// </returns>
		/// <exception cref="ArgumentNullException"><c>game</c> is
		/// null.</exception>
		private static bool IsDefaultClient(IGameKernel game)
		{
			if (game == null)
				throw new ArgumentNullException("game");
			return (game.Name == DefaultWorldName);
		}

		/// <summary>
		/// Loads the local game.
		/// </summary>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"Exceptions should not propagate past this point.")]
		public void LoadLocal()
		{
			lock (_sync)
			{
				try
				{
					IGameKernel check = null;
					if (_currentKernel != null)
						check = _currentKernel.Target;
					else if (_loadingKernel != null)
						check = _loadingKernel.Target;
					if (check != null)
					{
						if (IsDefaultClient(check) || IsBuiltInClient(check))
						{
							Quit();
							if (_log.IsWarnEnabled)
							{
								_log.Warn("Request to load the local game when " +
								          "the local game is active. Quitting instead.");
							}
							return;
						}
					}
					StartDefaultLoad();
				}
				catch (Exception exception)
				{
					Quit();
					if (_log.IsWarnEnabled)
					{
						_log.Warn(("Quitting because the default load was " +
						           "unable to complete successfully.\n") +
						          exception);
					}
				}
			}
		}

		/// <summary>
		/// Indicate that the game should quit.
		/// </summary>
		public void Quit()
		{
			_hasQuit = true;
			Thread.MemoryBarrier();
		}

		/// <summary>
		/// Starts the Built-In game loading.
		/// </summary>
		private void StartBuiltInLoad()
		{
			const string file =
				"Properties/BuiltInServiceConfiguration.xml";

			StartLoad(BuiltInWorldName, file, string.Empty);
		}

		/// <summary>
		/// Starts the default game loading.
		/// </summary>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"Exceptions should not propagate past this point.")]
		private void StartDefaultLoad()
		{
			try
			{
				string file = "World/" + _localWorldName +
				              "/Client/Properties/ServiceConfiguration.boo";
				string searchPaths = "World/" + _localWorldName +
				                     "/Client;Library/Client";
				StartLoad(_localWorldName, file, searchPaths);
			}
			catch (Exception exception)
			{
				if (_log.IsWarnEnabled)
				{
					_log.Warn(("Loading built-in client due to " +
					           "error with default client configuration:\n") +
					          exception);
				}
				StartBuiltInLoad();
			}
		}

		/// <summary>
		/// Starts loading the specified game.
		/// </summary>
		/// <param name="worldName">Name of the world.</param>
		/// <param name="configPath">The path to load configuration from.
		/// </param>
		/// <param name="searchPaths">The search paths used to look up
		/// assemblies.</param>
		/// <exception cref="ArgumentNullException"><c>worldName</c>
		/// is null.</exception>
		/// <exception cref="ArgumentNullException"><c>configPath</c>
		/// is null.</exception>
		/// <exception cref="ArgumentNullException"><c>searchPaths</c>
		/// is null.</exception>
		private void StartLoad(string worldName, string configPath,
		                       string searchPaths)
		{
			if (worldName == null)
				throw new ArgumentNullException("worldName");
			if (configPath == null)
				throw new ArgumentNullException("configPath");
			if (searchPaths == null)
				throw new ArgumentNullException("searchPaths");
			if (_log.IsInfoEnabled)
			{
				_log.Info(
					new StringBuilder("Starting load of ").Append(worldName)
						.Append(".").ToString());
			}
			CancelLoadNoLock();
			_loadingKernel = _gameKernelFactory.Create(worldName,
			                                           _parentKernel,
			                                           configPath,
			                                           searchPaths);
		}

		/// <summary>
		/// Updates the current game based on the time elapsed.
		/// </summary>
		/// <param name="elapsed">The elapsed time since last update.
		/// </param>
		/// <returns>The game to switch to.</returns>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"Exceptions during disposal can be ignored.")]
		public void Update(TimeSpan elapsed)
		{
			lock (_sync)
			{
				if (_loadingKernel == null)
				{
					if (_currentKernel == null)
						Quit();
					return;
				}
				IGameKernel loading = _loadingKernel.Target;
				if (!loading.Ready)
					return;
				ExternalProxy<IGameKernel> proxy = _loadingKernel;
				_loadingKernel = null;
				if (loading.Failure == null)
				{
					if (_log.IsInfoEnabled)
						_log.Info("Loading of " + loading + " succeeded.");
					ExternalProxy<IGameKernel> old = _currentKernel;
					_currentKernel = proxy;
					try
					{
						if (old != null)
							old.Dispose();
					}
					catch (Exception exception)
					{
						if (_log.IsWarnEnabled)
						{
							_log.Warn(
								"Error while disposing of an unused Game.",
								exception);
						}
					}
					return;
				}
				if (_log.IsWarnEnabled)
				{
					_log.Warn("Unable to load " + loading + ".",
					          loading.Failure);
				}
				if (IsBuiltInClient(loading))
				{
					Quit();
					if (_log.IsFatalEnabled)
					{
						_log.Fatal(
							"Unable to load even the built-in client. " +
							"Perhaps some configuration settings are incorrect.");
					}
				}
				else if (IsDefaultClient(loading))
					StartBuiltInLoad();
				else
					StartDefaultLoad();
				proxy.Dispose();
			}
		}
	}
}